/*
 * XInput1_3-dev - Replacement of xinput library to help with creating
 *                 drivers/debugging application
 *
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3.0 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.
 */

#include "stdafx.h"

#include "XInputCore.h"
#include "DeviceInfo.h"
#include "DriverComm.h"
#include "Utilities.h"
#include <strsafe.h>

static XINPUT_STATE DisabledState = {0};

XInputCore::APIFunction_t DeviceInfo::g_pfnGetStateDispatcher = DeviceInfo::Disabled::GetState;
XInputCore::APIFunction_t DeviceInfo::g_pfnSetVibrationDispatcher = DeviceInfo::Disabled::SetVibration;
XInputCore::APIFunction_t DeviceInfo::g_pfnGetBatteryInformationDispatcher = DeviceInfo::GetBatteryInformation;
XInputCore::APIFunction_t DeviceInfo::g_pfnGetCapabilitiesDispatcher = DeviceInfo::GetCapabilities;
XInputCore::APIFunction_t DeviceInfo::g_pfnGetKeystrokeDispatcher = DeviceInfo::GetKeystroke;
XInputCore::APIFunction_t DeviceInfo::g_pfnPowerOffController = DeviceInfo::PowerOffController;
XInputCore::APIFunction_t DeviceInfo::g_pfnWaitForGuideButton = DeviceInfo::WaitForGuideButton;
XInputCore::APIFunction_t DeviceInfo::g_pfnCancelGuideButtonWait = DeviceInfo::CancelGuideButtonWait;

HRESULT WINAPI DeviceInfo::CancelGuideButtonWait(PDEVICE_INFO pDeviceInfo, LPVOID *ppContext, uchar ucReserved)
{
	UNREFERENCED_PARAMETER(ppContext);
	UNREFERENCED_PARAMETER(ucReserved);
	return DriverComm::CancelGuideButtonWait(pDeviceInfo);
}

PDEVICE_INFO DeviceInfo::Create(HANDLE hDevice, wchar_t const *pwcsDeviceName)
{
	PDEVICE_INFO pDeviceInfo = (PDEVICE_INFO)LocalAlloc(LMEM_ZEROINIT, 96);

	if ( pDeviceInfo )
	{
		// XInput memsets here even though allocated with LMEM_ZEROINIT????
		memset(pDeviceInfo, 0, 96);

		pDeviceInfo->hDevice = INVALID_HANDLE_VALUE;
		pDeviceInfo->hWaitDevice = INVALID_HANDLE_VALUE;

		HANDLE hProcess = GetCurrentProcess();
		DuplicateHandle(hProcess, hDevice, hProcess, &(pDeviceInfo->hDevice), NULL, FALSE, DUPLICATE_SAME_ACCESS);

		pDeviceInfo->dwDeviceNameLength = wcslen(pwcsDeviceName) + 1;
		pDeviceInfo->pwchDeviceName = (wchar_t*)LocalAlloc(LMEM_ZEROINIT, sizeof(wchar_t) * pDeviceInfo->dwDeviceNameLength);

		if( SUCCEEDED(StringCchCopyW(pDeviceInfo->pwchDeviceName, pDeviceInfo->dwDeviceNameLength, pwcsDeviceName)) )
		{
			return pDeviceInfo;
		}
	}

	if ( pDeviceInfo )
	{
		if ( pDeviceInfo->pwchDeviceName )
			LocalFree(pDeviceInfo->pwchDeviceName);

		if ( pDeviceInfo->hDevice != INVALID_HANDLE_VALUE )
			CloseHandle(pDeviceInfo->hDevice);

		LocalFree(pDeviceInfo);
	}

	return NULL;
}

HRESULT DeviceInfo::Destroy(PDEVICE_INFO pDeviceInfo)
{
	if ( pDeviceInfo )
	{
		if ( pDeviceInfo->hWaitDevice != INVALID_HANDLE_VALUE )
			CloseHandle(pDeviceInfo->hWaitDevice);

		if ( pDeviceInfo->hDevice != INVALID_HANDLE_VALUE )
			CloseHandle(pDeviceInfo->hDevice);

		if ( pDeviceInfo->pwchDeviceName )
			LocalFree(pDeviceInfo->pwchDeviceName);

		return (LocalFree(pDeviceInfo) == NULL) ? S_OK : E_FAIL;
	}

	return S_OK;
}

HRESULT WINAPI DeviceInfo::Disabled::GetState(PDEVICE_INFO pDeviceInfo, LPVOID lpArgs[], uchar ArgC)
{
	PXINPUT_STATE pState = (PXINPUT_STATE)lpArgs[0];

	if ( pDeviceInfo->dwFlags & 1 )
	{
		HRESULT hResult = Utilities::SafeCopyToUntrustedBuffer(pState, &DisabledState, sizeof(XINPUT_STATE));
		if ( SUCCEEDED(hResult) )
		{
			pState->dwPacketNumber = pDeviceInfo->State.dwPacketNumber + 1;
		}
		return hResult;
	}

	return E_FAIL;
}

HRESULT WINAPI DeviceInfo::Disabled::SetVibration(PDEVICE_INFO pDeviceInfo, LPVOID lpArgs[], uchar ArgC)
{
	PXINPUT_VIBRATION pVibration = (PXINPUT_VIBRATION)lpArgs[0];

	if ( !(pDeviceInfo->dwFlags & 1) )
		return E_FAIL;

	if ( pVibration )
	{
		return Utilities::SafeCopyFromUntrustedBuffer(&pDeviceInfo->Vibration, pVibration, sizeof(XINPUT_VIBRATION));
	}

	DEVICE_INFO pTempDeviceInfo;
	memcpy(&pTempDeviceInfo, pDeviceInfo, sizeof(DEVICE_INFO));
	return DriverComm::SendDeviceVibration(&pTempDeviceInfo);
}

HRESULT WINAPI DeviceInfo::Enabled::GetState(PDEVICE_INFO pDeviceInfo, LPVOID lpArgs[], uchar ArgC)
{
	PXINPUT_STATE pState = (PXINPUT_STATE)lpArgs[0];

	if ( pDeviceInfo->dwFlags & 1 )
	{
		HRESULT hResult = DriverComm::GetLatestDeviceInfo(pDeviceInfo);
		if( FAILED(hResult))
			return hResult;

		return Utilities::SafeCopyToUntrustedBuffer(pState, &pDeviceInfo->State, sizeof(XINPUT_STATE));
	}

	return E_FAIL;
}

HRESULT WINAPI DeviceInfo::Enabled::SetVibration(PDEVICE_INFO pDeviceInfo, LPVOID lpArgs[], uchar ArgC)
{
	PXINPUT_VIBRATION pVibration = (PXINPUT_VIBRATION)lpArgs[0];

	if ( pDeviceInfo->dwFlags & 1 )
	{
		if(pVibration)
		{
			HRESULT hResult = Utilities::SafeCopyFromUntrustedBuffer(&pDeviceInfo->Vibration, pVibration, sizeof(XINPUT_VIBRATION));
			if ( FAILED(hResult) )
				return hResult;

			return DriverComm::SendDeviceVibration(pDeviceInfo);
		}
	}

	return E_FAIL;
}

HRESULT WINAPI DeviceInfo::GetAudioDeviceGuids(PDEVICE_INFO pDeviceInfo, PVOID *,uchar)
{
	// TODO: Implement
	return E_FAIL;
}

HRESULT WINAPI DeviceInfo::GetBatteryInformation(PDEVICE_INFO pDeviceInfo, LPVOID lpArgs[], uchar ArgC)
{
	BYTE DevType = *((BYTE*)lpArgs[0]);
	PXINPUT_BATTERY_INFORMATION pBatteryInformation = (PXINPUT_BATTERY_INFORMATION)lpArgs[1];

	if ( pDeviceInfo->dwFlags & 1 )
		return DriverComm::GetBatteryInformation(pDeviceInfo, DevType, pBatteryInformation);

	return E_FAIL;
}

HRESULT WINAPI DeviceInfo::GetCapabilities(PDEVICE_INFO pDeviceInfo, LPVOID lpArgs[], uchar ArgC)
{
	PXINPUT_CAPABILITIES pCapabilities = (PXINPUT_CAPABILITIES)lpArgs[0];

	if ( pDeviceInfo->dwFlags & 1 )
		return DriverComm::GetCapabilities(pDeviceInfo, pCapabilities);

	 return E_FAIL;
}

HRESULT WINAPI DeviceInfo::GetKeystroke(PDEVICE_INFO pDeviceInfo, LPVOID lpArgs[], uchar ArgC)
{
	// TODO: IMPLEMENT
	return E_FAIL;
}

HRESULT DeviceInfo::IsDeviceActive(PDEVICE_INFO pDeviceInfo)
{
	return ( pDeviceInfo->dwFlags & 1 ) ? S_OK : S_FALSE;
}

bool DeviceInfo::MinFillFromInterface(HANDLE hDevice, PDEVICE_INFO pDeviceInfo)
{
	if ( (hDevice != INVALID_HANDLE_VALUE) && pDeviceInfo )
	{
		// Unknown what information this containsS
		INTERFACE_INFORMATION OutBuffer = {0};

		if ( SUCCEEDED(DriverComm::GetDeviceInfoFromInterface(hDevice, &OutBuffer)) )
		{
			if( (OutBuffer.dwUsers & 0x80) == 0 )
			{
				memset((void *)pDeviceInfo, 0, 96);
				pDeviceInfo->hDevice = hDevice;
				pDeviceInfo->dwFlags &= 1;
				pDeviceInfo->hWaitDevice = INVALID_HANDLE_VALUE;
				pDeviceInfo->wVersion = OutBuffer.wVersion;
				pDeviceInfo->Dummy78 = OutBuffer.wVID;
				pDeviceInfo->Dummy80 = OutBuffer.wPID;
				return true;
			}
		}

		return false;
	}
	
	return false;
}

bool DeviceInfo::OnEnableSettingChanged(bool bEnabled)
{
	if ( bEnabled )
	{
		DeviceInfo::g_pfnGetStateDispatcher = DeviceInfo::Enabled::GetState;
		DeviceInfo::g_pfnSetVibrationDispatcher = DeviceInfo::Enabled::SetVibration;
	}
	else
	{
		DeviceInfo::g_pfnGetStateDispatcher = DeviceInfo::Disabled::GetState;
		DeviceInfo::g_pfnSetVibrationDispatcher = DeviceInfo::Disabled::SetVibration;
	}

	return bEnabled;
}

HRESULT WINAPI DeviceInfo::PowerOffController(PDEVICE_INFO pDeviceInfo, LPVOID lpArgs[], uchar ArgC)
{
	if ( pDeviceInfo->dwFlags & 1 )
		return DriverComm::PowerOffController(pDeviceInfo);

	 return E_FAIL;
}

void DeviceInfo::Recycle(PDEVICE_INFO pDeviceInfo)
{
	if ( pDeviceInfo )
	{
		HANDLE hDevice = pDeviceInfo->hDevice;
		wchar_t *pwchDeviceName = pDeviceInfo->pwchDeviceName;
		memset(pDeviceInfo, 0, 96);
		pDeviceInfo->hWaitDevice = INVALID_HANDLE_VALUE;
		pDeviceInfo->hDevice = hDevice;
		pDeviceInfo->pwchDeviceName = pwchDeviceName;
	}
}

struct ThreadContext
{
	HANDLE hWaitDevice;
	HANDLE *phEvent;
	BYTE bPort;
	PXINPUT_LISTEN_STATE pListenState;
};

DWORD WINAPI WaitForGuideButtonHelper(LPVOID lpParameter)
{
	ThreadContext *pThreadContext = (ThreadContext*)lpParameter;

	if( SUCCEEDED(DriverComm::WaitForGuideButton(pThreadContext->hWaitDevice, pThreadContext->bPort, pThreadContext->pListenState)) )
	{
		if ( *pThreadContext->phEvent )
			SetEvent(*pThreadContext->phEvent);
	}

	delete pThreadContext;

	return 0;
}

HRESULT WINAPI DeviceInfo::WaitForGuideButton(PDEVICE_INFO pDeviceInfo, LPVOID lpArgs[], uchar ArgC)
{
	PXINPUT_LISTEN_STATE pListenState = NULL;

	if( pDeviceInfo->dwFlags & 1 )
	{
		HANDLE *phEvent = (HANDLE*)lpArgs[0];
		PXINPUT_LISTEN_STATE pListenState = (PXINPUT_LISTEN_STATE)lpArgs[1];

		if( pDeviceInfo->hDevice != INVALID_HANDLE_VALUE )
		{
			pDeviceInfo->hDevice = CreateFileW(pDeviceInfo->pwchDeviceName, GENERIC_READ | GENERIC_WRITE,
				FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL, NULL);
		}

		if( pDeviceInfo->hDevice != INVALID_HANDLE_VALUE )
		{
			ThreadContext *pThreadContext = new ThreadContext;
			if ( pThreadContext )
			{
				pThreadContext->hWaitDevice = pDeviceInfo->hWaitDevice;
				pThreadContext->phEvent = phEvent;
				pThreadContext->bPort = (BYTE)pDeviceInfo->bPort;
				pThreadContext->pListenState = pListenState;

				if ( *pThreadContext->phEvent )
				{
					if ( !CreateThread(0, 0, WaitForGuideButtonHelper, pThreadContext, 0, 0) )
					{
						delete pThreadContext;
						pListenState->dwErrorCode = GetLastError();
						return E_FAIL;
					}
					
					return S_OK;
				}
				else
				{
					if( FAILED(XInputCore::Leave()) )
					{
						WaitForGuideButtonHelper(pThreadContext);

						delete pThreadContext;

						return S_FALSE;
					}

					pListenState->dwErrorCode = ERROR_OUTOFMEMORY;
					return E_OUTOFMEMORY;
				}
			}
		}
	}

	pListenState->dwErrorCode = ERROR_DEVICE_NOT_CONNECTED;
	return E_FAIL;
}